﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EnvDTE;
using EnvDTE80;
using VSLangProj;
using System.Reflection;
using SDILReader;

namespace SD007
{
    public class DetectorClass
    {
        protected const int NOP_HIGH_THRESHOLD = 5;
        protected const int LONG_METHOD_THRESHOLD = 20;
        protected const int WMC_VERY_HIGH_THRESHOLD = 80;
        protected const int WMC_HIGH_THRESHOLD = 60;
        protected const int NOPA_LOW_THRESHOLD = 2;
        protected const int NOAM_LOW_THRESHOLD = 3;
        protected const int NOPA_MANY_THRESHOLD = 6;
        protected const int NOAM_MANY_THRESHOLD = 10;
        protected const int LOC_HIGH_THRESHOLD = 65;
        protected const int CYCLO_HIGH_THRESHOLD = 6;

        protected const int MAXNESTING_SEVERAL_THRESHOLD = 0;
        protected const int NOAV_MANY_THRESHOLD = 0;

        protected const int ATFD_FEW_THRESHOLD = 4;

        protected VirtualPoint Point { get; set; }
        protected CodeFunction2 Function { get; set; }
        protected CodeClass2 Class { get; set; }
        public ClassAbstraction ClassAbstraction { get; set; }
        protected DTE2 _DTE2 { get; set; }
        protected OutputWindowPane OWp { get; set; }
        protected DetectorMessages DetectorMessages { get; set; }
        protected MethodAbstraction FunctionAbstraction { get; set; }
        protected AddinInterfaces Windows { get; set; }

        public DetectorClass(VirtualPoint point, DTE2 _dte, AddinInterfaces _windows)
        {
            this.Point = point;
            this._DTE2 = _dte;
            this.Windows = _windows;
        }

        public void GetCurrentFunction()
        {
            this.Function = (CodeFunction2)this.Point.get_CodeElement(vsCMElement.vsCMElementFunction);
            this.FunctionAbstraction = new MethodAbstraction(this.Function, this.Class,
                this.ClassAbstraction.InstanceVariables);
        }

        private void GetCurrentClass()
        {
            this.Class = (CodeClass2)this.Point.get_CodeElement(vsCMElement.vsCMElementClass);
            this.ClassAbstraction = new ClassAbstraction(this.Class);
        }

        public void Detect(DetectorMessages messages)
        {
            this.DetectorMessages = messages;
            this.GetCurrentClass();
            this.GetCurrentFunction();
            if (this.Function != null)
            {
                DetectLongParameterList();
                DetectBrainMethod();
            }
            if (this.Class != null)
            {
                DetectLargeClass();
                DetectGodClass();
                DetectDataClass();
            }
        }

        private void DetectLargeClass()
        {
            if (this.ClassAbstraction.WMC > WMC_VERY_HIGH_THRESHOLD)
            {
                //this.ShowMessage(new LargeClassErrorType(this.Class.StartPoint.Line,
                //    this.Class.StartPoint.DisplayColumn, this.Class.ProjectItem.FileNames[0]));
                LargeClassErrorType er = new LargeClassErrorType(this.Class.StartPoint.Line,
                    this.Class.StartPoint.DisplayColumn, this.Class.ProjectItem.FileNames[0]);
                er.WMCVal = this.ClassAbstraction.WMC;
                this.ShowMessage(er);
            }
        }

        private void DetectBrainMethod()
        {
            bool locGreaterThanHigh = this.FunctionAbstraction.LOC > LOC_HIGH_THRESHOLD;
            bool cycloGreaterThanOrEqualToHigh = this.FunctionAbstraction.CYCLO >= CYCLO_HIGH_THRESHOLD;
            bool maxNestingGreaterThanOrEqualToSeveral = this.FunctionAbstraction.MAXNESTING >= MAXNESTING_SEVERAL_THRESHOLD;
            bool noavGreaterThanMany = this.FunctionAbstraction.NOAV > NOAV_MANY_THRESHOLD;
            if (PluginOptions.DetectBrainMethod
                && locGreaterThanHigh
                && cycloGreaterThanOrEqualToHigh
                && maxNestingGreaterThanOrEqualToSeveral
                && noavGreaterThanMany)
            {
                BrainMethodErrorType er = new BrainMethodErrorType(this.Function.StartPoint.Line,
                    this.Function.StartPoint.DisplayColumn, this.Function.ProjectItem.FileNames[0]);
                er.LOCVal = this.FunctionAbstraction.LOC;
                er.CYCLOVal = this.FunctionAbstraction.CYCLO;
                er.MAXNESTINGVal = this.FunctionAbstraction.MAXNESTING;
                er.NOAVVal = this.FunctionAbstraction.NOAV;
                this.ShowMessage(er);
            }
        }

        private void DetectLongParameterList()
        {
            bool nopGreaterThanHigh = this.FunctionAbstraction.NOP > NOP_HIGH_THRESHOLD;
            if (nopGreaterThanHigh)
            {
                LongParameterListErrorType er = new LongParameterListErrorType(this.Function.StartPoint.Line,
                    this.Function.StartPoint.DisplayColumn, this.Function.ProjectItem.FileNames[0]);
                er.NOPVal = this.FunctionAbstraction.NOP;
                this.ShowMessage(er);
            }
        }

        private void DetectGodClass()
        {
            bool wmcEqualOrGreaterThanVeryHigh = this.ClassAbstraction.WMC > WMC_VERY_HIGH_THRESHOLD;
            bool tccLessThanOneThird = this.ClassAbstraction.TCC < (1M / 3.0M);
            bool atfdGreatherThanFew = this.ClassAbstraction.ATFD > ATFD_FEW_THRESHOLD;
            if (PluginOptions.DetectGodClass && wmcEqualOrGreaterThanVeryHigh && tccLessThanOneThird && atfdGreatherThanFew)
            {
                GodClassErrorType er = new GodClassErrorType(this.Class.StartPoint.Line,
                    this.Class.StartPoint.DisplayColumn, this.Class.ProjectItem.FileNames[0]);
                er.WMCVal = this.ClassAbstraction.WMC;
                er.TCCVal = this.ClassAbstraction.TCC;
                er.ATFDVal = this.ClassAbstraction.ATFD;
                this.ShowMessage(er);
            }
        }

        private void DetectDataClass()
        {
            bool revealsDataRatherThanService = this.ClassAbstraction.WOC < (1.0M / 3.0M);
            int noapNoam = this.ClassAbstraction.NOPA + this.ClassAbstraction.NOAM;
            bool noapNoamGreaterThanAFew = noapNoam > (NOPA_LOW_THRESHOLD + NOAM_LOW_THRESHOLD);
            bool noapNoamGreatheThanMany = noapNoam > (NOPA_MANY_THRESHOLD + NOAM_MANY_THRESHOLD);
            bool wmcLessThanHigh = this.ClassAbstraction.WMC < WMC_HIGH_THRESHOLD;
            bool wmcLessThanVeryHigh = this.ClassAbstraction.WMC < WMC_VERY_HIGH_THRESHOLD;
            
            if (revealsDataRatherThanService && 
                ((noapNoamGreaterThanAFew && wmcLessThanHigh) || (noapNoamGreatheThanMany && wmcLessThanVeryHigh)))
            {
                DataClassErrorType er = new DataClassErrorType(this.Class.StartPoint.Line,
                    this.Class.StartPoint.DisplayColumn, this.Class.ProjectItem.FileNames[0]);
                er.NOPAVal = this.ClassAbstraction.NOPA;
                er.NOAMVal = this.ClassAbstraction.NOAM;
                er.WMCVal = this.ClassAbstraction.WMC;
                er.WOCVal = this.ClassAbstraction.WOC;
                this.ShowMessage(er);
            }
        }

        protected void ShowMessage(CodeElement codeElement, string message)
        {
            Project proj = codeElement.ProjectItem.ContainingProject;
            ProjectItem projItem = codeElement.ProjectItem;
            this.DetectorMessages.AddError(proj, projItem, message, codeElement.StartPoint.Line, codeElement.StartPoint.DisplayColumn);
        }

        protected void ShowMessage(ErrorType type)
        {
            ((TestUserControl)this.Windows.MainWindow.Object).AddError(type);
        }

        protected ErrorType LoadErrorTypeValues(ErrorType errorType, CodeElement codeElement)
        {
            errorType.Column = codeElement.StartPoint.DisplayColumn;
            errorType.Line = codeElement.StartPoint.Line;
            errorType.File = codeElement.ProjectItem.FileNames[0];
            return errorType;
        }
    }
}
